package edu.udo.sopra10.chaturaji.gui;

import java.awt.Dimension;
import java.util.HashMap;

import org.apache.batik.swing.JSVGCanvas;
import org.apache.batik.swing.svg.SVGDocumentLoaderEvent;
import org.apache.batik.swing.svg.SVGDocumentLoaderListener;
import org.w3c.dom.Element;
import org.w3c.dom.svg.SVGDocument;

import edu.udo.sopra10.chaturaji.entities.coordinates.Position;
import edu.udo.sopra10.chaturaji.entities.transfer.Piece;
import edu.udo.sopra10.chaturaji.entities.transfer.Piece.PieceType;

/**
 * Panel to display a square in the board.
 * 
 * The class uses CSS class names to apply settings to the underlaying vector
 * graphics. Components of class names are separated by a space and contain in
 * case of the piece class both the piece type (king, elephant, horse, boat and
 * pawn) and the player's color (yellow, green, red and blue) and in case of
 * the square class the square fill type (full, outline and inline) and the
 * respective color.
 * 
 * @author Patrick Westerhoff
 */
public class SquarePanel extends JSVGCanvas
{
	private static final long serialVersionUID = -5873964627955025281L;
	
	/**
	 * No player.
	 */
	public static final byte PLAYER_NONE = -1;
	
	/**
	 * First player (yellow).
	 */
	public static final byte PLAYER_YELLOW = 0;
	
	/**
	 * Second player (blue).
	 */
	public static final byte PLAYER_BLUE = 1;
	
	/**
	 * Third player (red).
	 */
	public static final byte PLAYER_RED = 2;
	
	/**
	 * Fourth player (green).
	 */
	public static final byte PLAYER_GREEN = 3;
	
	/**
	 * Square fill signalizing a selectable piece.
	 */
	public static final String ACTION_SELECT = "border";
	
	/**
	 * Square fill signalizing a possible move.
	 */
	public static final String ACTION_MOVE = "inner";
	
	/**
	 * Square fill signalizing a possible capture.
	 */
	public static final String ACTION_CAPTURE = "outer";
	
	/**
	 * Square fill signalizing a possible pawn promotion.
	 */
	public static final String ACTION_PROMOTE = "full";
	
	/**
	 * Square fill signalizing a possible boat triumph.
	 */
	public static final String ACTION_TRIUMPH = "full";
	
	/**
	 * Square fill signalizing a hinted move.
	 */
	public static final String ACTION_HINT = "full";
	
	/**
	 * Orientation of a piece.
	 * 
	 */
	public enum Orientation
	{
		UP, RIGHT, DOWN, LEFT;
	}
	
	private static HashMap<Piece.PieceType, String> PIECE = new HashMap<PieceType, String>( 5 );
	private static HashMap<Byte, String> PLAYER = new HashMap<Byte, String>( 5 );
	
	static
	{
		PIECE.put( Piece.PieceType.KING, "king" );
		PIECE.put( Piece.PieceType.ELEPHANT, "elephant" );
		PIECE.put( Piece.PieceType.HORSE, "horse" );
		PIECE.put( Piece.PieceType.BOAT, "boat" );
		PIECE.put( Piece.PieceType.PAWN, "pawn" );
		PLAYER.put( (byte) -1, "" );
		PLAYER.put( (byte) 0, " yellow" );
		PLAYER.put( (byte) 1, " blue" );
		PLAYER.put( (byte) 2, " red" );
		PLAYER.put( (byte) 3, " green" );
	}
	
	private Piece.PieceType pieceType;
	private byte playerId;
	private String squareClass;
	private Orientation orientation;
	
	private Dimension forcedPreferredSize;
	private Position position;
	
	
	/**
	 * Constructs a square panel with a piece of the given type, associated to
	 * the specified player, with the given square type and orientation.
	 * 
	 * @param pieceType the piece to display.
	 * @param playerId the player the piece is associated to.
	 * @param squareClass the square type to display.
	 * @param orientation the orientation of the displayed piece.
	 */
	public SquarePanel ( Piece.PieceType pieceType, byte playerId, String squareClass, Orientation orientation )
	{
		this.orientation = orientation;
		this.pieceType = pieceType;
		this.playerId = playerId;
		this.squareClass = squareClass;
		
		this.addSVGDocumentLoaderListener( new SVGPieceLoaderListener() );
		this.setEnableZoomInteractor( true );
		this.setBackground( null );
		
		this.loadSVGDocument( getClass().getResource( "/square.svg" ).toString() );
		this.setMinimumSize( new Dimension( 16, 16 ) );
	}
	
	/**
	 * Constructs a square panel with a piece of the given type, associated to
	 * the specified player, with the given square type and the default
	 * orientation.
	 * 
	 * @param pieceType the piece to display.
	 * @param playerId the player the piece is associated to.
	 * @param squareClass the square type to display.
	 */
	public SquarePanel ( Piece.PieceType pieceType, byte playerId, String squareClass )
	{
		this( pieceType, playerId, squareClass, Orientation.UP );
	}
	
	/**
	 * Constructs a square panel with a piece of the given type, associated to
	 * the specified player, with an unspecified (transparent) square type and
	 * the default orientation.
	 * 
	 * @param pieceType the piece to display.
	 * @param playerId the player the piece is associated to.
	 */
	public SquarePanel ( Piece.PieceType pieceType, byte playerId )
	{
		this( pieceType, playerId, "", Orientation.UP );
	}
	
	/**
	 * Constructs an empty square panel without a piece and with an unspecified
	 * (transparent) square type.
	 * 
	 */
	public SquarePanel ()
	{
		this( null, PLAYER_NONE, "", Orientation.UP );
	}
	
	
	/**
	 * {@inheritDoc}
	 * 
	 */
	@Override
	public void setMySize ( Dimension d )
	{
		if ( this.forcedPreferredSize != null )
			super.setMySize( this.forcedPreferredSize );
		else
			super.setMySize( d );
	}
	
	/**
	 * Sets the forced preferred size of the panel.
	 * 
	 * @param d the preferred size to be enforced.
	 */
	public void setForcedPreferredSize ( Dimension d )
	{
		this.forcedPreferredSize = d;
		this.setPreferredSize( d );
	}
	
	/**
	 * Updates the displayed SVG with the internal {@link SVGDocument}.
	 * 
	 */
	public void updateDisplay ()
	{
		this.stopThenRun( new Runnable()
			{
				@Override
				public void run ()
				{
					installSVGDocument( svgDocument );
				}
			} );
	}
	
	/**
	 * Sets the class name of the given element and updates the display. A
	 * rendering is not requested when the class name didn't change.
	 * 
	 * @param element element node which class attribute should be changed.
	 * @param className new class name.
	 */
	private void setClassAndUpdate ( Element element, String className )
	{
		if ( !element.getAttribute( "class" ).equals( className ) )
		{
			element.setAttribute( "class", className );
			this.updateDisplay();
		}
	}
	
	/**
	 * Sets the piece to the given piece type, associated to the given player.
	 * 
	 * @param pieceType the piece type.
	 * @param playerId the player.
	 */
	public void setPiece ( Piece.PieceType pieceType, byte playerId )
	{
		this.pieceType = pieceType;
		this.playerId = playerId;
		
		if ( svgDocument == null )
			return;
		
		String className;
		if ( this.pieceType == null || this.playerId == PLAYER_NONE )
			className = "";
		else
			className = PIECE.get( this.pieceType ) + PLAYER.get( this.playerId );
		
		setClassAndUpdate( svgDocument.getDocumentElement(), className );
	}
	
	/**
	 * Resets the displayed piece.
	 */
	public void resetPiece ()
	{
		setPiece( null, PLAYER_NONE );
	}
	
	/**
	 * Returns the current piece type.
	 * 
	 * @return the piece type.
	 */
	public Piece.PieceType getPieceType ()
	{
		return this.pieceType;
	}
	
	/**
	 * Returns the current player id.
	 * 
	 * @return the player id.
	 */
	public byte getPlayerId ()
	{
		return this.playerId;
	}
	
	/**
	 * Sets the displayed square class.
	 * 
	 * @param className square type to display.
	 */
	public void setSquare ( String className )
	{
		this.squareClass = className;
		
		if ( svgDocument == null )
			return;
		
		setClassAndUpdate( svgDocument.getElementById( "square" ), this.squareClass );
	}
	
	/**
	 * Sets the displayed square.
	 * 
	 * @param squareType the square type.
	 * @param squareColor the square color.
	 */
	public void setSquare ( String squareType, String squareColor )
	{
		this.setSquare( squareType + " " + squareColor );
	}
	
	/**
	 * Sets the displayed square.
	 * 
	 * @param squareType the square type.
	 * @param playerId the player associated to the square.
	 */
	public void setSquare ( String squareType, byte playerId )
	{
		this.setSquare( squareType + PLAYER.get( playerId ) );
	}
	
	/**
	 * Returns the current square class.
	 * 
	 * @return the current square class.
	 */
	public String getSquare ()
	{
		return this.squareClass;
	}
	
	/**
	 * Sets the displayed orientiation of the piece.
	 * 
	 * @param orientation the piece orientation.
	 */
	public void setOrientation ( Orientation orientation )
	{
		this.orientation = orientation;
		
		if ( svgDocument == null )
			return;
		
		String pieceTransformation = "";
		String shadowTransformation = "";
		switch ( orientation )
		{
			case RIGHT:
				pieceTransformation = "rotate(90,32,32)";
				shadowTransformation = "rotate(90,33,33)";
				break;
			
			case DOWN:
				pieceTransformation = "rotate(180,32,32)";
				shadowTransformation = "rotate(180,33,33)";
				break;
			
			case LEFT:
				pieceTransformation = "rotate(270)";
				shadowTransformation = "rotate(270)";
				break;
		}
		
		svgDocument.getElementById( "piece" ).setAttribute( "transform", pieceTransformation );
		svgDocument.getElementById( "shadow" ).setAttribute( "transform", shadowTransformation );
		this.updateDisplay();
	}
	
	/**
	 * Returns the current orientation of the piece.
	 * 
	 * @return the current piece orientation.
	 */
	public Orientation getOrientation ()
	{
		return this.orientation;
	}
	
	/**
	 * Sets the current position of the square.
	 * 
	 * @param position the position.
	 */
	public void setPosition ( Position position )
	{
		this.position = position;
	}
	
	/**
	 * Returns the current position of the square.
	 * 
	 * @return the current position.
	 */
	public Position getPosition ()
	{
		return this.position;
	}
	
	/**
	 * Simple SVGDocumentLoaderListener that only implements
	 * {@link SVGDocumentLoaderListener#documentLoadingCompleted} and applies
	 * previously set class names or transformations to the correct element
	 * nodes.
	 * 
	 * @author Patrick Westerhoff
	 */
	private class SVGPieceLoaderListener implements SVGDocumentLoaderListener
	{
		@Override
		public void documentLoadingCancelled ( SVGDocumentLoaderEvent event )
		{
		}
		
		@Override
		public void documentLoadingCompleted ( SVGDocumentLoaderEvent event )
		{
			SVGDocument document = event.getSVGDocument();
			
			if ( pieceType != null && playerId != PLAYER_NONE )
				document.getDocumentElement().setAttribute( "class", PIECE.get( pieceType ) + PLAYER.get( playerId ) );
			
			if ( squareClass != "" )
				document.getElementById( "square" ).setAttribute( "class", squareClass );
			
			if ( orientation != Orientation.UP )
				setOrientation( orientation );
		}
		
		@Override
		public void documentLoadingFailed ( SVGDocumentLoaderEvent event )
		{
		}
		
		@Override
		public void documentLoadingStarted ( SVGDocumentLoaderEvent event )
		{
		}
	}
}
